UNPKG

11.2 kBJavaScriptView Raw
1// Hacking too much time
2
3// Based on Node.js Module class sources:
4// https://github.com/nodejs/node/blob/master/lib/module.js
5
6'use strict';
7
8var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
9
10var _getIterator = require('babel-runtime/core-js/get-iterator')['default'];
11
12var _Set = require('babel-runtime/core-js/set')['default'];
13
14var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
15
16exports.__esModule = true;
17
18var _fs = require('fs');
19
20var _fs2 = _interopRequireDefault(_fs);
21
22var _path2 = require('path');
23
24var _path3 = _interopRequireDefault(_path2);
25
26var _module2 = require('module');
27
28var _module3 = _interopRequireDefault(_module2);
29
30var _toolsLog = require('./tools/log');
31
32var _toolsLog2 = _interopRequireDefault(_toolsLog);
33
34var _helpers = require('./helpers');
35
36var _toolsSerializeJavascript = require('./tools/serialize-javascript');
37
38var _toolsSerializeJavascript2 = _interopRequireDefault(_toolsSerializeJavascript);
39
40var original_findPath = _module3['default']._findPath;
41
42var Require_hacker = (function () {
43 function Require_hacker(options) {
44 var _this = this;
45
46 _classCallCheck(this, Require_hacker);
47
48 this.preceding_abstract_path_resolvers = [];
49 this.abstract_path_resolvers = [];
50 this.abstract_path_resolved_modules = {};
51
52 // // take the passed in options
53 // this.options = clone(options)
54
55 // logging
56 this.log = new _toolsLog2['default']('require-hook', { debug: options.debug }); // this.options.debug
57
58 // instrument Module._findPath
59 // https://github.com/nodejs/node/blob/master/lib/module.js#L335-L341
60 _module3['default']._findPath = function () {
61 for (var _len = arguments.length, parameters = Array(_len), _key = 0; _key < _len; _key++) {
62 parameters[_key] = arguments[_key];
63 }
64
65 var request = parameters[0];
66 // const paths = parameters[1]
67
68 // preceeding resolvers
69 for (var _iterator = _this.preceding_abstract_path_resolvers, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _getIterator(_iterator);;) {
70 var _ref;
71
72 if (_isArray) {
73 if (_i >= _iterator.length) break;
74 _ref = _iterator[_i++];
75 } else {
76 _i = _iterator.next();
77 if (_i.done) break;
78 _ref = _i.value;
79 }
80
81 var resolver = _ref;
82
83 var resolved = resolver.resolve(request);
84 if (typeof resolved !== 'undefined') {
85 return resolved;
86 }
87 }
88
89 // original Node.js loader
90 var filename = original_findPath.apply(undefined, parameters);
91 if (filename !== false) {
92 return filename;
93 }
94
95 // rest resolvers
96 for (var _iterator2 = _this.abstract_path_resolvers, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _getIterator(_iterator2);;) {
97 var _ref2;
98
99 if (_isArray2) {
100 if (_i2 >= _iterator2.length) break;
101 _ref2 = _iterator2[_i2++];
102 } else {
103 _i2 = _iterator2.next();
104 if (_i2.done) break;
105 _ref2 = _i2.value;
106 }
107
108 var resolver = _ref2;
109
110 var resolved = resolver.resolve(request);
111 if (typeof resolved !== 'undefined') {
112 return resolved;
113 }
114 }
115
116 return false;
117 };
118 }
119
120 // validation
121
122 // installs a require() hook for paths
123 // which don't exist in the filesystem
124 //
125 // (if these paths exist in the filesystem
126 // then use the .hook(extension, resolve) method instead)
127 //
128 // id - a meaningful textual identifier
129 //
130 // resolver - a function which takes two parameters:
131 //
132 // the path to be resolved
133 //
134 // a function which flushes require() cache for this path
135 // with no parameters
136 //
137 // must return a javascript CommonJS module source code
138 // (i.e. "module.exports = ...", etc)
139 //
140 // returns an object with an .undo() method
141 //
142
143 Require_hacker.prototype.global_hook = function global_hook(id, resolver) {
144 var _this2 = this;
145
146 var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
147
148 validate.global_hook(id, resolver);
149
150 var resolver_entry = {
151 id: id,
152 resolve: function resolve(path) {
153 var resolved_path = path + '.' + id;
154
155 // CommonJS module source code
156 var source = resolver(path);
157
158 if (!_helpers.exists(source)) {
159 return;
160 }
161
162 // const flush_cache = () => delete require.cache[resolved_path]
163 delete require.cache[resolved_path];
164
165 _this2.abstract_path_resolved_modules[resolved_path] = source;
166
167 return resolved_path;
168 }
169 };
170
171 if (options.precede_node_loader) {
172 this.preceding_abstract_path_resolvers.push(resolver_entry);
173 } else {
174 this.abstract_path_resolvers.push(resolver_entry);
175 }
176
177 var hook = this.hook(id, function (path) {
178 var source = _this2.abstract_path_resolved_modules[path];
179 delete _this2.abstract_path_resolved_modules[path];
180 return source;
181 });
182
183 var result = {
184 unmount: function unmount() {
185 // javascript arrays still have no .remove() method in the XXI-st century
186 _this2.preceding_abstract_path_resolvers = _this2.preceding_abstract_path_resolvers.filter(function (x) {
187 return x !== resolver_entry;
188 });
189 _this2.abstract_path_resolvers = _this2.abstract_path_resolvers.filter(function (x) {
190 return x !== resolver_entry;
191 });
192 hook.unmount();
193 }
194 };
195
196 return result;
197 };
198
199 // installs a require() hook for the extension
200 //
201 // extension - a file extension to hook into require()s of
202 // (examples: 'css', 'jpg', 'js')
203 //
204 // resolve - a function that takes two parameters:
205 //
206 // the path requested in the require() call
207 //
208 // and a fallback function (fall back to default behaviour)
209 // with no parameters
210 //
211 // must return a javascript CommonJS module source code
212 // (i.e. "module.exports = ...", etc)
213 //
214
215 Require_hacker.prototype.hook = function hook(extension, resolve) {
216 var _this3 = this;
217
218 this.log.debug('Hooking into *.' + extension + ' files loading');
219
220 // validation
221 validate.extension(extension);
222 validate.resolve(resolve);
223
224 // dotted extension
225 var dot_extension = '.' + extension;
226
227 // keep original extension loader
228 var original_loader = _module3['default']._extensions[dot_extension];
229
230 // display a warning in case of extension loader override
231 if (original_loader) {
232 // output a debug message in case of extension loader override,
233 // not a warning, so that it doesn't scare people
234 this.log.debug('-----------------------------------------------');
235 this.log.debug('Overriding an already existing require() hook ');
236 this.log.debug('for file extension ' + dot_extension);
237 this.log.debug('-----------------------------------------------');
238 }
239
240 // the list of cached modules
241 var cached_modules = new _Set();
242
243 // set new loader for this extension
244 _module3['default']._extensions[dot_extension] = function (module, filename) {
245 _this3.log.debug('Loading source code for ' + filename);
246
247 // fallback flag
248 var aborted = false;
249
250 // var source = fs.readFileSync(filename, 'utf8')
251 var source = resolve(filename, function () {
252 _this3.log.debug('Fallback to original loader');
253
254 // fallen back
255 aborted = true;
256
257 // this message would appear if there was no loader
258 // for the extension of the filename
259 if (_path3['default'].extname(filename) !== dot_extension) {
260 _this3.log.info('Trying to load "' + _path3['default'].basename(filename) + '" as a "*' + dot_extension + '"');
261 }
262
263 // load the file with the original loader
264 (original_loader || _module3['default']._extensions['.js'])(module, filename);
265 });
266
267 // if fallen back - exit
268 if (aborted) {
269 return;
270 }
271
272 // add this file path to the list of cached modules
273 cached_modules.add(filename);
274
275 // compile javascript module from its source
276 // https://github.com/nodejs/node/blob/master/lib/module.js#L379
277 module._compile(source, filename);
278 };
279
280 var result = {
281 // uninstall the hook
282 unmount: function unmount() {
283 // clear require() cache for this file extension
284 for (var _iterator3 = cached_modules, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _getIterator(_iterator3);;) {
285 var _ref3;
286
287 if (_isArray3) {
288 if (_i3 >= _iterator3.length) break;
289 _ref3 = _iterator3[_i3++];
290 } else {
291 _i3 = _iterator3.next();
292 if (_i3.done) break;
293 _ref3 = _i3.value;
294 }
295
296 var _path = _ref3;
297
298 delete require.cache[_path];
299 }
300
301 // mount the original loader for this file extension
302 _module3['default']._extensions[dot_extension] = original_loader;
303 }
304 };
305
306 return result;
307 };
308
309 // // uninstalls a previously installed require() hook for the extension
310 // //
311 // // extension - the file extension for which to uninstall
312 // // the previously installed require() hook
313 // //
314 // unhook(extension)
315 // {
316 // this.log.debug(`Unhooking from .${extension} files loading`)
317 //
318 // // validation
319 // validate.extension(extension)
320 //
321 // // dotted extension
322 // const dot_extension = `.${extension}`
323 //
324 // // verify that the hook exists in the first place
325 // if (Object.keys(this.original_loaders).indexOf(dot_extension) < 0)
326 // {
327 // throw new Error(`Require() hook wasn't previously installed for ${dot_extension} files`)
328 // }
329 //
330 // // uninstall the hook
331 // Module._extensions[dot_extension] = this.original_loaders[dot_extension]
332 // delete this.original_loaders[dot_extension]
333 // }
334 return Require_hacker;
335})();
336
337exports['default'] = Require_hacker;
338var validate = {
339 extension: function extension(_extension) {
340 // if (typeof extension !== 'string')
341 // {
342 // throw new Error(`Expected string extension. Got ${extension}`)
343 // }
344
345 if (_path3['default'].extname('test.' + _extension) !== '.' + _extension) {
346 throw new Error('Invalid file extension ' + _extension);
347 }
348 },
349
350 resolve: function resolve(_resolve) {
351 if (typeof _resolve !== 'function') {
352 throw new Error('Resolve should be a function. Got ' + _resolve);
353 }
354 },
355
356 global_hook: function global_hook(id, resolver) {
357 if (!id) {
358 throw new Error('You must specify global hook id');
359 }
360
361 if (_path3['default'].extname('test.' + id) !== '.' + id) {
362 throw new Error('Invalid global hook id. Expected a valid file extension.');
363 }
364
365 validate.resolve(resolver);
366 }
367};
368
369// returns a CommonJS modules source.
370Require_hacker.to_javascript_module_source = function (anything) {
371 // if the asset source wasn't found - return an empty CommonJS module
372 if (!_helpers.exists(anything)) {
373 return 'module.exports = undefined';
374 }
375
376 // if it's already a common js module source
377 if (typeof anything === 'string' && is_a_module_declaration(anything)) {
378 return anything;
379 }
380
381 // generate javascript module source code based on the `source` variable
382 return 'module.exports = ' + _toolsSerializeJavascript2['default'](anything);
383};
384
385// detect if it is a CommonJS module declaration
386function is_a_module_declaration(text) {
387 return text.indexOf('module.exports = ') === 0 || /\s+module\.exports = .+/.test(text);
388}
389module.exports = exports['default'];
390
391// original_loaders = {}
392//# sourceMappingURL=require hacker.js.map
\No newline at end of file